﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Xml.Linq;
using Castle.Core;
using Castle.Windsor;
using NHibernate.Mapping.ByCode;
using Xenta.Attributes;
using Xenta.Enums;
using Xenta.Events;
using Xenta.Exceptions;
using Xenta.Plugins;
using Xenta.Security;
using Xenta.Structs;
using Xenta.Tasks;
using Xenta.Triggers;
using Xenta.Utils;
using log4net;

namespace Xenta
{
    /// <summary>
    /// Exposes the infrastructure interface.
    /// </summary>
    /// <remarks>
    /// The infrastructure is a central part of the framework.
    /// </remarks>
    public interface IInfrastructure : IDisposable
    {
        #region Properties

        /// <summary>
        /// Gets the name of the infrastructure.
        /// </summary>
        string Name
        {
            get;
        }

        /// <summary>
        /// Gets the environment.
        /// </summary>
        IEnvironment Env
        {
            get;
        }

        /// <summary>
        /// Gets the unit pool.
        /// </summary>
        IUnitOfWorkPool UoWPool
        {
            get;
        }

        /// <summary>
        /// Gets the security auditor.
        /// </summary>
        ISecurityAuditor SecurityAuditor
        {
            get;
        }

        /// <summary>
        /// Gets the event broker.
        /// </summary>
        IEventBroker EventBroker
        {
            get;
        }

        /// <summary>
        /// Gets the log instance.
        /// </summary>
        ILog Log
        {
            get;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the dependency resolver.
        /// </summary>
        /// <returns>The dependency resolver instance.</returns>
        IDependencyResolver DependencyResolver();

        /// <summary>
        /// Determines, if the dependency is registered.
        /// </summary>
        /// <param name="id">The dependency identifier.</param>
        /// <returns>true if dependency is registered; otherwise false.</returns>
        bool CheckDependency(string id);

        /// <summary>
        /// Determines, if dependency is registered and implements the 
        /// required service interface.
        /// </summary>
        /// <typeparam name="TDependency">The service interface to check.</typeparam>
        /// <param name="id">The identifier of required dependency.</param>
        /// <returns>
        /// true if dependency is registered and implements the 
        /// required interface; otherwise false.
        /// </returns>
        bool CheckDependency<TDependency>(string id)
            where TDependency : IDependency;

        /// <summary>
        /// Determines, if dependency is registered and implements the 
        /// required service interface.
        /// </summary>
        /// <typeparam name="TDependency">The service interface to check.</typeparam>
        /// <returns>
        /// true if dependency is registered and implements the 
        /// required interface; otherwise false.
        /// </returns>
        bool CheckDependency<TDependency>()
            where TDependency : IDependency;

        /// <summary>
        /// Determines, if dependency is registered and implements the 
        /// required service interface.
        /// </summary>
        /// <param name="t">The type of service interface to check.</param>
        /// <returns>
        /// true if dependency is registered and implements the 
        /// required interface; otherwise false.
        /// </returns>
        bool CheckDependency(Type t);

        /// <summary>
        /// Gets the collection of available dependencies.
        /// </summary>
        /// <typeparam name="TDependency">The service interface to check.</typeparam>
        /// <returns>The dependency collection.</returns>
        IEnumerable<DependencyInfo> ListDependencies<TDependency>()
            where TDependency : IDependency;

        /// <summary>
        /// Gets the dependency.
        /// </summary>
        /// <param name="id">The dependency identifier.</param>
        /// <returns>The dependency.</returns>
        DependencyInfo? DependencyInfo(string id);

        #endregion
    }

    /// <summary>
    /// The IInfrastructure implementation.
    /// </summary>
    public sealed class InfrastructureImpl : IInfrastructure
    {
        #region Constants

        /// <summary>
        /// Defines the plugins file name.
        /// </summary>
        public const string PLUGINS_FILENAME = "plugins.xml";

        /// <summary>
        /// Defines the NHibernate configuration file name.
        /// </summary>
        public const string NH_CFG_FILENAME = "nhibernate.xml";

        #endregion

        #region Fields

        private readonly string _name;
        private readonly IWindsorContainer _ioc;
        private readonly ILog _log;
        private readonly IEnvironment _environment;
        private readonly Dictionary<ComponentModel, DependencyInfo> _dependencies;
        private readonly IUnitOfWorkPool _uowPool;
        private readonly ISecurityAuditor _securityAuditor;
        private readonly IEventBroker _eventBroker;

        #endregion

        #region Ctors

        private InfrastructureImpl(string name,
            IEnvironment environment,
            IUnitOfWorkPool uowPool,
            ISecurityAuditor securityAuditor,
            IEventBroker eventBroker,
            IWindsorContainer ioc,
            ILog log)
        {
            _name = name;
            _ioc = ioc;
            _log = log;
            _uowPool = uowPool;
            _securityAuditor = securityAuditor;
            _eventBroker = eventBroker;
            _environment = environment;
            _dependencies = new Dictionary<ComponentModel, DependencyInfo>();

            _ioc.Register(Castle.MicroKernel.Registration.Component
                .For<IInfrastructure>()
                .Instance(this));
            foreach(var i in _ioc.Kernel.GetAssignableHandlers(typeof(IDependency)))
            {
                var depModel = i.ComponentModel;
                var depType = depModel.Implementation;
                string depName = depType.Name;
                string depID = depName;
                string depVersion = String.Empty;
                var depAtttr = ReflectionUtility.Attribute<DependencyAttribute>(depType);
                if(depAtttr != null)
                {
                    if(!String.IsNullOrWhiteSpace(depAtttr.Name))
                        depName = depAtttr.Name;
                    depID = depAtttr.ID;
                    depVersion = depAtttr.Version;
                }
                _dependencies.Add(depModel, new DependencyInfo
                {
                    Name = depName,
                    Version = depVersion,
                    ID = depID,
                    Type = depType.Name
                });
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the name of the infrastructure.
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
        }

        /// <summary>
        /// Gets the environment.
        /// </summary>
        public IEnvironment Env
        {
            get
            {
                return _environment;
            }
        }

        /// <summary>
        /// Gets the unit pool.
        /// </summary>
        public IUnitOfWorkPool UoWPool
        {
            get
            {
                return _uowPool;
            }
        }

        /// <summary>
        /// Gets the security auditor.
        /// </summary>
        public ISecurityAuditor SecurityAuditor
        {
            get
            {
                return _securityAuditor;
            }
        }

        /// <summary>
        /// Gets the event broker.
        /// </summary>
        public IEventBroker EventBroker
        {
            get
            {
                return _eventBroker;
            }
        }

        /// <summary>
        /// Gets the log instance.
        /// </summary>
        public ILog Log
        {
            get
            {
                return _log;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the dependency resolver.
        /// </summary>
        /// <returns>The dependency resolver instance.</returns>
        public IDependencyResolver DependencyResolver()
        {
            return new DependencyResolverImpl(_ioc);
        }

        /// <summary>
        /// Determines, if the dependency is registered.
        /// </summary>
        /// <param name="id">The dependency name.</param>
        /// <returns>true if dependency is registered; otherwise false.</returns>
        public bool CheckDependency(string id)
        {
            return !String.IsNullOrWhiteSpace(id) && _dependencies.Any(x => x.Value.ID.Equals(id));
        }

        /// <summary>
        /// Determines, if dependency is registered and implements the 
        /// required service interface.
        /// </summary>
        /// <typeparam name="TDependency">The service interface to check.</typeparam>
        /// <param name="id">The identifier of required dependency.</param>
        /// <returns>
        /// true if dependency is registered and implements the 
        /// required interface; otherwise false.
        /// </returns>
        public bool CheckDependency<TDependency>(string id)
            where TDependency : IDependency
        {
            var t = typeof(TDependency);
            return !String.IsNullOrWhiteSpace(id) && 
                _dependencies.Any(x => 
                    x.Value.ID.Equals(id) && 
                    x.Key.Services.Contains(t));
        }

        /// <summary>
        /// Determines, if dependency is registered and implements the 
        /// required service interface.
        /// </summary>
        /// <typeparam name="TDependency">The service interface to check.</typeparam>
        /// <returns>
        /// true if dependency is registered and implements the 
        /// required interface; otherwise false.
        /// </returns>
        public bool CheckDependency<TDependency>()
            where TDependency : IDependency
        {
            return CheckDependency(typeof(TDependency));
        }

        /// <summary>
        /// Determines, if dependency is registered and implements the 
        /// required service interface.
        /// </summary>
        /// <param name="t">The type of service interface to check.</param>
        /// <returns>
        /// true if dependency is registered and implements the 
        /// required interface; otherwise false.
        /// </returns>
        public bool CheckDependency(Type t)
        {
            if(!typeof(IDependency).IsAssignableFrom(t))
                return false;
            return _dependencies.Any(x => x.Key.Services.Contains(t));
        }

        /// <summary>
        /// Gets the collection of available dependencies.
        /// </summary>
        /// <typeparam name="TDependency">The service interface to list.</typeparam>
        /// <returns>The dependency collection.</returns>
        public IEnumerable<DependencyInfo> ListDependencies<TDependency>()
            where TDependency : IDependency
        {
            var t = typeof(TDependency);
            return _dependencies
                .Where(x => x.Key.Services.Contains(t))
                .Select(x => x.Value);
        }

        /// <summary>
        /// Gets the dependency.
        /// </summary>
        /// <param name="id">The dependency identifier.</param>
        /// <returns>The dependency info or null.</returns>
        public DependencyInfo? DependencyInfo(string id)
        {
            if(String.IsNullOrWhiteSpace(id))
                return null;
            return _dependencies.Values.SingleOrDefault(x => x.ID.Equals(id));
        }

        /// <summary>
        /// Disposes the UoW pool and associated IoC container.
        /// </summary>
        public void Dispose()
        {
            _uowPool.Dispose();
            _ioc.Dispose();
        }

        #endregion

        #region Static Methods

        /// <summary>
        /// Produces an infrastructure instance.
        /// </summary>
        /// <param name="name">The name of an infrastructure.</param>
        /// <param name="env">The execution environment.</param>
        /// <returns>The infrastructure instance.</returns>
        public static IInfrastructure Build(string name, IEnvironment env)
        {
            if(env == null)
                throw new ArgumentNullException("env");
            if(String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            if(!SystemHelper.GetDirectoryRights(env.WorkingDir, env.WorkingIdentity)
                .HasFlag(FileSystemRights.Modify))
            {
                throw new ApplicationException("Does not have the required permissions on a working directory.");
            }
            if(!SystemHelper.GetDirectoryRights(env.DataDir, env.WorkingIdentity)
                .HasFlag(FileSystemRights.Modify))
            {
                throw new ApplicationException("Does not have the required permissions on a data directory.");
            }

            string pluginsFilePath = Path.Combine(env.DataDir, PLUGINS_FILENAME);
            var xPluginsOld = File.Exists(pluginsFilePath) 
                ? XElement.Load(pluginsFilePath) 
                : null;
            var xPluginsNew = new XElement("plugins");
            var assemblies = AppDomain.CurrentDomain.GetAssemblies()
                .Union(ReflectionUtility.LoadAssemblies(env.BinDir));
            var types = ReflectionUtility.AssemblyTypes(assemblies, x => x.IsClass && !x.IsAbstract);
            var ioc = new WindsorContainer();

            foreach(var depType in types.Where(x => typeof(IDependency).IsAssignableFrom(x)))
            {
                string depName = depType.Name;
                if(typeof(IPlugin).IsAssignableFrom(depType))
                {
                    var xPlugin = xPluginsOld != null 
                        ? xPluginsOld.Elements("plugin").SingleOrDefault(x => 
                            x.Attribute("name").Value.Equals(depName)) 
                        : null;
                    if(xPlugin == null)
                    {
                        xPlugin = new XElement("plugin", 
                            new XAttribute("name", depName), 
                            new XAttribute("enabled", Boolean.TrueString));
                    }
                    xPluginsNew.Add(xPlugin);
                    if(!xPlugin.Attribute("enabled").Value.ParseAs<Boolean>())
                        continue;
                }
                var depLifestyle = DependencyLifestyle.Transient;
                var depAttr = ReflectionUtility.Attribute<DependencyAttribute>(depType);
                if(depAttr != null)
                {
                    depName = depAttr.ID;
                    depLifestyle = depAttr.Lifestyle;
                }
                var com = Castle.MicroKernel.Registration.Component
                    .For<IDependency>()
                    .ImplementedBy(depType);
                com.Forward(depType.ListInterfaces(x => x != typeof(IDependency) &&
                    typeof(IDependency).IsAssignableFrom(x)));
                switch(depLifestyle)
                {
                    case DependencyLifestyle.Singleton:
                        com.LifestyleSingleton();
                        break;
                    case DependencyLifestyle.Scoped:
                        com.LifestyleScoped();
                        break;
                    default:
                        com.LifestyleTransient();
                        break;
                }
                if(typeof(IInitDependencyOnResolve).IsAssignableFrom(depType))
                    com.OnCreate(x => (x as IInitDependencyOnResolve).Initialize());
                com.Named(depName);
                ioc.Register(com);
            }
            xPluginsNew.Save(pluginsFilePath);

            string nhCfgFilePath = Path.Combine(env.DataDir, NH_CFG_FILENAME);
            var nhCfg = new NHibernate.Cfg.Configuration()
                .Configure(nhCfgFilePath);
            var modelMapper = new ModelMapper();

            foreach(var mapType in types.Where(x => !String.IsNullOrEmpty(x.Namespace) && 
                x.Namespace.Equals("Xenta.Entities") && x.Name.EndsWith("Mapping")))
            {
                modelMapper.AddMapping(mapType);
            }
            nhCfg.AddMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities());
            nhCfg.SetProperty(NHibernate.Cfg.Environment.SqlExceptionConverter,
                typeof(NHibernateExceptionConverter).AssemblyQualifiedName);

            var eventInterpreter = NHibernateEventInterpreter.Attach(nhCfg.EventListeners);
            var eventBroker = ioc.Resolve<IEventBroker>();
            var sessionFactory = nhCfg.BuildSessionFactory();
            var uowFactory = new UnitOfWorkFactoryImpl(sessionFactory);
            var uowPool = new UnitOfWorkPoolImpl(uowFactory);
            var securityAuditor = new SecurityAuditorImpl();
            var log = LogManager.GetLogger(name);
            var instance = new InfrastructureImpl(name, 
                env, 
                uowPool, 
                securityAuditor, 
                eventBroker, 
                ioc, 
                log);
                    
            eventInterpreter.Attach(eventBroker);
            ioc.ResolveAll<ITrigger>().ForEach(x => x.Attach(eventBroker));
            ioc.ResolveAll<ITaskScheduler>().ForEach(x => x.Start());

            return instance;
        }

        #endregion
    }
}
